using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Threading;

namespace SuperBall3D
{

    public enum GameState
    {
        StartMenu, Playing
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public partial class SuperBallGame : Microsoft.Xna.Framework.Game
    {
        GameState currentGameState;
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        bool pause;
        Player player;
        Pointer pointer;
        KeyboardState previousKeyboardState;
        Texture2D PauseMenu;
        SpriteFont HUDfont;
        Song backgroundMusic;
        bool FirstTime  = true;
        public bool GameOver = false;
        Texture2D[] skyboxTextures;
        Texture2D openbackground;
        Model skyboxModel;
        Effect effect;

        int screenWidth, screenHeight;

        SoundEffect[] winsounds = new SoundEffect[3];
        SoundEffect[] losesounds = new SoundEffect[6];
        SoundEffect[] pointsounds = new SoundEffect[5];
        Random random = new Random();

        int deaths = 0;

        public SuperBallGame()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferHeight = 600;
            graphics.PreferredBackBufferWidth = 800;
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            MediaPlayer.Volume = 0.5f;
            currentGameState = GameState.StartMenu;
            player = new Player(this, new Vector3(0, -70, 0), 1.25f);
            pointer = new Pointer(this, new Vector3(100, -325, 3300), .8f, .8f, .8f);
            Components.Add(player);
            Components.Add(pointer);
            Generate();
            previousKeyboardState = Keyboard.GetState();

            if (FirstTime==true) // only needs to start once
            {
                backgroundMusic = Content.Load<Song>("BGmusic");
                MediaPlayer.IsRepeating = true;
                MediaPlayer.Play(backgroundMusic);
            }
            
                               
            Window.Title = "SuperBall 3D";
            Camera.AspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            screenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            screenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;

            if (FirstTime)
            {
                // Create a new SpriteBatch, which can be used to draw textures.
                spriteBatch = new SpriteBatch(GraphicsDevice);
                PauseMenu = Content.Load<Texture2D>("Textures\\menuScreen");
                HUDfont = Content.Load<SpriteFont>("GameFont");
                spriteBatch = new SpriteBatch(GraphicsDevice);
                effect = Content.Load<Effect>("effect");
                skyboxModel = LoadModel("Models\\skybox2", out skyboxTextures);
                winsounds[0] = Content.Load<SoundEffect>("SoundEffects\\applause");
                winsounds[1] = Content.Load<SoundEffect>("SoundEffects\\kids_cheer");
                winsounds[2] = Content.Load<SoundEffect>("SoundEffects\\trumpet_win");
                
                losesounds[0] = Content.Load<SoundEffect>("SoundEffects\\boo-crowd-02");
                losesounds[1] = Content.Load<SoundEffect>("SoundEffects\\evil_laugh");
                losesounds[2] = Content.Load<SoundEffect>("SoundEffects\\Glass_smashing");
                losesounds[3] = Content.Load<SoundEffect>("SoundEffects\\nee2");
                losesounds[4] = Content.Load<SoundEffect>("SoundEffects\\cuckoo1");
                losesounds[5] = Content.Load<SoundEffect>("SoundEffects\\cuckoo2");

                pointsounds[0] = Content.Load<SoundEffect>("SoundEffects\\cashregister");
                pointsounds[1] = Content.Load<SoundEffect>("SoundEffects\\Goodjoke");
                pointsounds[2] = Content.Load<SoundEffect>("SoundEffects\\HARPUP");
                pointsounds[3] = Content.Load<SoundEffect>("SoundEffects\\JAZZ");
                pointsounds[4] = Content.Load<SoundEffect>("SoundEffects\\orchestra1");
                openbackground = Content.Load<Texture2D>("Textures\\openscreen");
                

            }
            
            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            //if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            //    this.Exit();
            KeyboardState currentKeyboardState = Keyboard.GetState();
            

            if (currentGameState == GameState.StartMenu)
            {
                if (currentKeyboardState.IsKeyDown(Keys.Escape) && !previousKeyboardState.IsKeyDown(Keys.Escape))
                {
                    this.Exit();
                    return;
                }
                else if (currentKeyboardState.IsKeyDown(Keys.Enter) && previousKeyboardState.IsKeyUp(Keys.Enter))
                {
                    if (FirstTime) FirstTime = false;
                    else Restart(false);
                    Pause = false;
                    GameOver = false;
                    currentGameState = GameState.Playing;
                }
                //return;
            }

            else if (currentGameState == GameState.Playing)
            {


                if (currentKeyboardState.IsKeyDown(Keys.Enter) && previousKeyboardState.IsKeyUp(Keys.Enter))
                {
                    Pause = !Pause;
                    if (Pause) MediaPlayer.Volume = 0.15f;
                    else MediaPlayer.Volume = 0.5f;
                }


                if (SuperBallObject.platformList.Count == 0)
                {
                    SoundEffect win = winsounds[random.Next(0, 3)];
                    win.Play();
                    Thread.Sleep(3000);
                    Restart(true);
                }

                // TODO: Add your update logic here

                if (!Pause)
                {
                    pointer.UpdatePointer(player.Position, player.playerFacingDirection);
                    Physics.Update(this);
                    base.Update(gameTime);
                }
                else if (currentKeyboardState.IsKeyDown(Keys.Escape) && previousKeyboardState.IsKeyUp(Keys.Escape))
                {
                    currentGameState = GameState.StartMenu;
                    MediaPlayer.Volume = 0.5f;
                }
                
            }
            previousKeyboardState = currentKeyboardState;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        string helptext = "Controls:\n  WASD or arrows = Move.\n  Space = Jump.\n" +
            "  Enter = Pause/unpause.\n  Esc = Quit to Main Menu.\n\nRules:\n"+
            " 1. The arrow points to your next goal.\n"+
            " 2. The white buttons update your goal.\n"+
            " 3. Push blocks by rolling into them.";

        string menutext = "How to Play:\n  WASD or arrows = Move.\n  Space = Jump.\n" +
            "  Esc = Quit.\n  Enter = Start game.\n\nRules:\n"+
            " 1. The arrow points to your next goal.\n"+
            " 2. The white buttons update your goal.\n"+
            " 3. Push blocks by rolling into them.";
                                                
        protected override void Draw(GameTime gameTime)
        {
            // TODO: Add your drawing code here
            // Draw main menu
            Rectangle screenRectangle = new Rectangle(0, 0, screenWidth, screenHeight);

            if (currentGameState == GameState.StartMenu)
            {
                GraphicsDevice.Clear(Color.DarkBlue);
                spriteBatch.Begin();
                spriteBatch.Draw(openbackground, screenRectangle, Color.White);
                spriteBatch.Draw(PauseMenu, new Vector2(150, 40), Color.White);
                spriteBatch.DrawString(HUDfont, menutext, new Vector2(170, 120), Color.Yellow);
                if (GameOver)
                    spriteBatch.DrawString(HUDfont, "Try again?", new Vector2(340, 12), Color.Yellow);
                if(deaths>0)
                    spriteBatch.DrawString(HUDfont, deaths.ToString(), new Vector2(750, 20), Color.Yellow);
                spriteBatch.End();
            }

            else if (currentGameState == GameState.Playing)
            {
                //int pSpeed = (int)(Math.Sqrt(player.KineticEnergy) / 9.5f);
                GraphicsDevice.Clear(Color.SkyBlue);
                DrawSkybox();
                base.Draw(gameTime);
                
                spriteBatch.Begin();
                Vector3 XZVelocity = player.Velocity;
                XZVelocity.Y = 0;
                spriteBatch.DrawString(HUDfont, "Speed = " + Math.Truncate((XZVelocity.Length() / 25)).ToString() + " mph",
                    new Vector2(5, 20), Color.Black);
                if (Pause)
                {
                    spriteBatch.Draw(PauseMenu, new Vector2(150, 40), Color.White);
                    spriteBatch.DrawString(HUDfont, helptext, new Vector2(170, 120), Color.Yellow);
                    if (deaths == 1)
                    {
                        spriteBatch.DrawString(HUDfont, "You have died " + deaths.ToString() + " time.",
                            new Vector2(170, 390), Color.Orange);
                    }
                    else if (deaths > 1)
                    {
                        spriteBatch.DrawString(HUDfont, "You have died " + deaths.ToString() + " times.",
                            new Vector2(170, 390), Color.Orange);
                    }
                }
                else
                {
                    spriteBatch.DrawString(HUDfont, "ENTER = Pause",
                       new Vector2(5, screenHeight - 10), Color.Black);
                }

                spriteBatch.End();
                GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                
            }
        }

        public bool Pause
        {
            get { return pause; }
            set { pause = value; }
        }

        public void PlayPointSound()
        {
            SoundEffect s = pointsounds[random.Next(0, 5)];
            s.Play();
        }

        public void PlayLosingSound()
        {
            SoundEffect loss = losesounds[random.Next(0, 6)];
            loss.Play();
            deaths++;
        }
        
        public void Restart(bool endInVictory)
        {
            if (endInVictory) deaths = 0;
            Components.Clear();
            SuperBallObject.AllSuperBallObjects.Clear();
            SuperBallObject.DynamicSuperBallObjects.Clear();
            SuperBallObject.platformList.Clear();
            
            Initialize();

            
        }

        private Model LoadModel(string assetName, out Texture2D[] textures)
        {
            Model newModel = Content.Load<Model>(assetName);
            textures = new Texture2D[newModel.Meshes.Count];
            int i = 0;
            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (BasicEffect currentEffect in mesh.Effects)
                    textures[i++] = currentEffect.Texture;
            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    meshPart.Effect = effect.Clone();
            return newModel;
        }

        private void DrawSkybox()
        {
            SamplerState ss = new SamplerState();
            ss.AddressU = TextureAddressMode.Clamp;
            ss.AddressV = TextureAddressMode.Clamp;
            GraphicsDevice device = graphics.GraphicsDevice;
            device.SamplerStates[0] = ss;

            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = false;
            device.DepthStencilState = dss;

            Matrix[] skyboxTransforms = new Matrix[skyboxModel.Bones.Count];
            skyboxModel.CopyAbsoluteBoneTransformsTo(skyboxTransforms);
            int i = 0;
            foreach (ModelMesh mesh in skyboxModel.Meshes)
            {
                foreach (Effect currentEffect in mesh.Effects)
                {
                    Matrix worldMatrix = skyboxTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(160) * Matrix.CreateTranslation(player.Position + Vector3.UnitY * -1600);
                    currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"];
                    currentEffect.Parameters["xWorld"].SetValue(worldMatrix);
                    currentEffect.Parameters["xView"].SetValue(Camera.ViewMatrix);
                    currentEffect.Parameters["xProjection"].SetValue(Camera.ProjectionMatrix);
                    currentEffect.Parameters["xTexture"].SetValue(skyboxTextures[i++]);
                }
                mesh.Draw();
            }
            dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            device.DepthStencilState = dss;

        }

    }
}
